package genetic;

import q3.ReadFile;
import q3.cliquePhenotype;

import java.io.File;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class GeneticAlgorithm {

	List<Genotype> population;
	int populationSize;
	Phenotype prototype;

	public GeneticAlgorithm(Phenotype prototype){
		this.population = new LinkedList<Genotype>();
		this.prototype = prototype;
	}

	public void init(int populationSize){
		this.populationSize = populationSize;
		population.clear();
		for (int i = 0; i < populationSize; i++) {
			population.add(prototype.createRandomInstance());
		}
	}

	public void runAlgorithm(int numGenerations, double Pc, double Pm, String resultFile){
		PrintWriter out = openFile(resultFile);
		
		double[] initStats = getStatistics();
		System.out.println("Initial population: Best - " + initStats[0] + ", Average - " + initStats[1]);
		out.println(0 + "\t" + initStats[0] + "\t" + initStats[1]);

		for (int generation=1; generation<numGenerations; generation++){
			population = getMatingPoolRouletteWheel();
			List<Genotype> newPopulation = new LinkedList<Genotype>();
			for (int i=0; i<populationSize-1; i+=2){	//breed
				Genotype p1 = population.get(i);
				Genotype p2 = population.get(i+1);
				Genotype[] offsprings;

				double rand = Math.random();	
				if (rand<Pc){		//create offsprings
					offsprings = prototype.crossover(p1, p2);
				} else {			//copy parents
					offsprings = new Genotype[]{p1.clone(), p2.clone()};	
				}

				newPopulation.add(prototype.mutate(offsprings[0], Pm));
				newPopulation.add(prototype.mutate(offsprings[1], Pm));
			}
			population = newPopulation;
			double[] stats = getStatistics();
			System.out.println("Generation "+generation+": Best - " + stats[0] + ", Average - " + stats[1]);
			out.println(generation + "\t" + stats[0] + "\t" + stats[1]);
		}

		System.out.println("Best genotype on final generation: " + getBest());
		
		out.close();
	}



	private List<Genotype> getMatingPoolRouletteWheel(){	//TODO rounding is needed to overcome double errors
		List<Genotype> matingPool = new LinkedList<Genotype>();	

		double fitnessSum = 0;
		for (Genotype g: population){
			fitnessSum = fitnessSum + prototype.fitness(g);
		}
		//		System.out.println("fitness sum: " + fitnessSum);
		for (int i=0; i<populationSize; i++){	//creating the mating pool
			double rand = Math.random()*fitnessSum;
			for (Genotype g: population){
				//				System.out.println("rand = " + rand);
				rand = rand - prototype.fitness(g);
				if (rand<=0){
					matingPool.add(g);
					break;
				}
			}
		}
		Collections.shuffle(matingPool);
		return matingPool;
	}

	private double[] getStatistics(){
		double maxFitness = 0;
		double averageFitness = 0;
		for (Genotype g: population){
			double fitness = prototype.fitness(g);
			maxFitness = Math.max(fitness , maxFitness);
			averageFitness = averageFitness + fitness;
		}
		averageFitness = averageFitness/(double)population.size();
		return new double[]{ maxFitness , averageFitness};	
	}

	private Genotype getBest(){
		double maxFitness = Double.MIN_VALUE;
		Genotype best = null;
		for (Genotype g: population){
			double fitness = prototype.fitness(g);
			maxFitness = Math.max(fitness , maxFitness);
			if (maxFitness == fitness){
				best = g;
			}
		}
		return best;
	}

	private PrintWriter openFile(String filename){
		try{
			File f = new File(filename);
			f.delete();
			f.createNewFile();
			PrintWriter out = new PrintWriter(f);
			return out;
		}catch (Exception e) {
			return null;
		}
	}

public static void main(String[] args) {
	ReadFile rf = new ReadFile();
	rf.openFile("c-fat500-1.txt");
	System.out.println("size "+rf.size);
	System.out.println("max "+rf.max);
	GeneticAlgorithm b = new GeneticAlgorithm(new cliquePhenotype(rf.size, rf));
//	b.init(100);
//	b.runAlgorithm(10000, 0.7, 0.001, "10000q3.txt");
//	b.init(100);
//	b.runAlgorithm(5000, 0.7, 0.001, "5000q3.txt");
//	b.init(100);
//	b.runAlgorithm(2000, 0.7, 0.001, "2000q3.txt");
	b.init(100);
	b.runAlgorithm(500, 0.7, 0.001, "500q3.txt");
	b.init(100);
	b.runAlgorithm(100, 0.7, 0.001, "100q3.txt");
}

}